package com.dianwoba.core.response;

import java.io.IOException;
import java.util.Enumeration;
import java.util.Set;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.alibaba.dubbo.rpc.RpcException;
import com.dianwoba.core.exception.BusinessException;
import com.dianwoba.core.exception.UnexpectedException;
import com.dianwoba.core.search.PagingSearchable;
import com.dianwoba.middleware.trace.client.TraceUtil;
import com.dianwoba.utils.RequestUtil;
import com.google.common.base.Strings;

/**
 * @author Zhu
 * @date 2015-5-18
 * @version 0.0.1
 * @description 负责将返回转换成统一消息格式 序列化为的格式如下: { "status": 1, "data": data, "msg": ""
 *              } 其中被注解的方法只需要关心data的内容即可
 */
@Order(100)
public class ResponseJsonAspect {

	@Resource
	private MappingJackson2HttpMessageConverter converter;

	@Resource
	private JsonObjectMapper jsonObjectMapper;

	private final Logger logger = LoggerFactory.getLogger(getClass());
	
	private final String FAIL_MSG = "操作失败";
	
	protected static final String reqParamsStringKey = "reqParamsString";
	
	protected static final String reqStartMsecsKey = "reqStartMsecs";

	/**
	 * 拦截所有@ResponseBody
	 * 
	 * @author Zhu
	 * @date 2015-5-18上午11:32:26
	 * @description
	 */
//	@Pointcut("execution(* com.dianwoba.redcliff.*.web.controller..*.*(..)) && @annotation(com.dianwoba.core.response.ResponseJson)")
	public void responseBodyPointCut() {

	}

	/**
	 * 请求执行之前执行
	 * 
	 * @param request
	 */
//	@Before(value = "responseBodyPointCut()")
	public void before(JoinPoint jp) {
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
				.getRequestAttributes()).getRequest();
		request.setAttribute(reqStartMsecsKey, System.currentTimeMillis());
		
		String uri = request.getRequestURI();

		StringBuilder params = new StringBuilder(uri+ " " + request.getMethod()
				+ " with parameters [");
		@SuppressWarnings("rawtypes")
		Enumeration e = request.getParameterNames();
		while (e.hasMoreElements()) {
			String p = (String) e.nextElement();
			params.append(p).append("=").append(request.getParameter(p))
					.append(",");
		}
		params.append("]");
		
		request.setAttribute(reqParamsStringKey, params.toString());

		Object[] args = jp.getArgs();
		pagingHandle(args);
	}

	/**
	 * 请求完成执行
	 * 
	 * @param returnValue
	 * @param request
	 * @param response
	 * @throws IOException
	 * @throws HttpMessageNotWritableException
	 */
//	@AfterReturning(value = "responseBodyPointCut()", returning = "ret")
	public void after(Object ret) throws HttpMessageNotWritableException,
			IOException {

		ResponseJsonBean responseBase = new ResponseJsonBean();
		responseBase.setData(ret);

		HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder
				.getRequestAttributes()).getResponse();
		HttpOutputMessage outputMessage = new ServletServerHttpResponse(response);
		converter.write(responseBase, MediaType.APPLICATION_JSON, outputMessage);
		
		String returnValue = jsonObjectMapper.writeValueAsString(responseBase);
		int len = StringUtils.length(returnValue);
		returnValue = len > 1000 ? (StringUtils.substring(returnValue, 0, 1000) + "...}") : returnValue;

		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
				.getRequestAttributes()).getRequest();
		complete(returnValue, request, response);

		shutdownResponse(response);
	}

	/**
	 * 请求完成执行
	 * 
	 * @param returnValue
	 * @param request
	 * @param response
	 */
	protected void complete(String returnValue, HttpServletRequest request,
			HttpServletResponse response) {
		long reqStartMsecs = (Long) request.getAttribute(reqStartMsecsKey);
		String reqParamsString = (String) request
				.getAttribute(reqParamsStringKey);
		
		long cost = System.currentTimeMillis() - reqStartMsecs;
		String log = reqParamsString + ",result is " + returnValue
			+ ",cost(ms):" + cost + ",remoteAddr=" + RequestUtil.getNginxRealIp(request);

		if(TraceUtil.getTraceId() != null)
			log += ", traceId: " + TraceUtil.getTraceId();

		if (cost < 2000) {
			logger.info(log);
		} else {
			logger.warn(log);
		}
	}

	/**
	 * 异常处理
	 * 
	 * @author Zhu
	 * @date 2015-5-18下午6:01:46
	 * @description
	 * @param jp
	 * @param error
	 * @throws Throwable
	 */
//	@AfterThrowing(pointcut = "responseBodyPointCut()", throwing = "error")
	public void exception(JoinPoint jp, Throwable error) throws Throwable {
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
				.getRequestAttributes()).getRequest();
		ResponseJsonBean responseBase = new ResponseJsonBean();
		responseBase.setStatus(0);
		
		String reqParamsString = (String) request.getAttribute(reqParamsStringKey);
		String prefix = (StringUtils.isBlank(reqParamsString) ? "" : reqParamsString + ",method ") + jp.getSignature().getName();
		long reqStartMsecs = (Long) request.getAttribute(reqStartMsecsKey);
		long cost = System.currentTimeMillis() - reqStartMsecs;
        Throwable rootCause = error.getCause();
        if (error instanceof BusinessException || (rootCause != null && rootCause instanceof BusinessException)) {
            logger.warn(prefix
                    + " throws BusniessException,cost(ms):" + cost + ",traceId:" + Strings.nullToEmpty(TraceUtil.getTraceId()), error);
            responseBase
					.setMsg(StringUtils.isBlank(error.getMessage()) ? FAIL_MSG
							: error.getMessage());
			BusinessException businessException = (BusinessException) error;
			responseBase.setErrorCode(businessException.getErrorCode());
			responseBase.setExtraMsg(businessException.getExtraMsg());
		} else if (error instanceof UnexpectedException) {
			logger.error(prefix
                    + " throws UnexpectedException!,cost(ms):" + cost + ",traceId:" + Strings.nullToEmpty(TraceUtil.getTraceId()), error);
            responseBase.setMsg(FAIL_MSG);
		} else if ((error instanceof RpcException) && (error.getCause() instanceof ConstraintViolationException)) {
			Set<ConstraintViolation<?>> violations = ((ConstraintViolationException) error.getCause())
					.getConstraintViolations(); // 可以拿到一个验证错误详细信息的集合
			if (violations != null && violations.size() > 0) {
				logger.error(prefix + " validate failed!,cost(ms):" + cost + ",traceId:"
						+ Strings.nullToEmpty(TraceUtil.getTraceId()), error);
				responseBase.setMsg(violations.iterator().next().getMessage());
			}
		} else {
            logger.error(prefix + " occors error,cost(ms):" + cost + ",traceId:" + Strings.nullToEmpty(TraceUtil.getTraceId()), error);
            responseBase.setMsg(FAIL_MSG);
		}
		HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder
				.getRequestAttributes()).getResponse();
		HttpOutputMessage outputMessage = new ServletServerHttpResponse(
				response);
		converter
				.write(responseBase, MediaType.APPLICATION_JSON, outputMessage);
		complete(
				String.format("exception:{msg:%s,errorCode:%s}",
						responseBase.getMsg(), responseBase.getErrorCode()),
				request, response);
		shutdownResponse(response);
	}

	protected void shutdownResponse(HttpServletResponse response)
			throws IOException {
		response.getOutputStream().close();
	}
	
	/**
	 * 处理分页的情况
	 * 
	 * @param args
	 */
	protected void pagingHandle(Object[] args) {
		if (ArrayUtils.isNotEmpty(args)) {
			for (Object arg : args) {
				if (arg instanceof PagingSearchable) {
					PagingSearchable pageSearch = (PagingSearchable) arg;
					if (!pageSearch.isPaging()) {
						pageSearch.setPerPage(999999);
					}
				}
			}
		}
	}

}
